﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Net;
using JOATServer.SocietyFramework.Core.XML;

namespace JOATServer.SocietyFramework.Core
{
    /// <summary>
    /// Automaton that manages the automata transfers between the local application and the remote application.
    /// Author: Micu Andrei
    /// </summary>
    public class AutomataTransferAutomaton : Automaton 
    {
	    protected static string CONNECTION_STATE = "CONNECTION_STATE";
	    protected static string IDLE_STATE = "IDLE_STATE";
	    protected static string SEND_AUTOMATON_STATE = "SEND_AUTOMATON_STATE";
	    protected static string PROCESS_AUTOMATON_REQUEST = "PROCESS_AUTOMATON_REQUEST";
	
	    protected SocietyManager societyManager;
	
	    protected TcpListener tcpListener;
        protected TcpClient tcpClient;
        protected BinaryReader inputStream;
        protected BinaryWriter outputStream;

        protected Thread pullingThread;
        protected bool pullRunning;
        protected Thread pushingThread;
        protected bool pushRunning;

        protected MessageList automatonsToSend;
	
	    protected enum Errors { PULL_ERROR, PUSH_ERROR };
	
	    public AutomataTransferAutomaton(SocietyManager societyManager) {
		    this.societyManager = societyManager;
            this.currentStateName = CONNECTION_STATE;
            this.automatonsToSend = new MessageList();
            this.pullRunning = false;
            this.pushRunning = false;
            this.tcpListener = new TcpListener(IPAddress.Any, SocietyInformation.AutomataTransferPort);
            this.tcpListener.Start();

            states.Add(CONNECTION_STATE, new ConnectionState(this));
            states[CONNECTION_STATE].transition = new ConnectionTransition(this);

            states.Add(IDLE_STATE, new NullState(this));
            states[IDLE_STATE].transition = new IdleTransition(this);

            states.Add(SEND_AUTOMATON_STATE, new SendAutomatonState(this));
            states[SEND_AUTOMATON_STATE].transition = new SimpleTransition(this, IDLE_STATE);

            states.Add(PROCESS_AUTOMATON_REQUEST, new ProcessAutomatonRequestState(this));
            states[PROCESS_AUTOMATON_REQUEST].transition = new SimpleTransition(this, IDLE_STATE);
	    }


        /* CONNECTION_STATE:  Automaton tries to connect to the server*/
        protected class ConnectionState : State
        {
            public ConnectionState(BaseAutomaton owner) : base(owner) { }

            public override void StateCode()
            {
                AutomataTransferAutomaton automaton = (AutomataTransferAutomaton)Owner;
                try
                {
                    if (automaton.tcpClient != null)
                    {
                        automaton.automatonsToSend.CancelRemoving();
                        automaton.tcpClient.Close();
                    }
                    MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME, 
                        new Message("AutomataTransferAutomaton", "Waiting client connection for automata transfer..."));
                    automaton.tcpClient = automaton.tcpListener.AcceptTcpClient();
                    if (automaton.tcpClient.Connected)
                    {
                        automaton.inputStream = new BinaryReader(automaton.tcpClient.GetStream());
                        automaton.outputStream = new BinaryWriter(automaton.tcpClient.GetStream());

                        automaton.pullRunning = true;
                        automaton.pushRunning = true;

                        automaton.pullingThread = new Thread(() => { automaton.PullAutomatons(); });
                        automaton.pushingThread = new Thread(() => { automaton.PushAutomatons(); });

                        automaton.pullingThread.Name = "AutomataTransferPullingThread";
                        automaton.pushingThread.Name = "AutomataTransferPushingThread";

                        automaton.pullingThread.Start();
                        automaton.pushingThread.Start();

                        MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                            new Message("AutomataTransferAutomaton", "Client connected for automata transfer."));
                    }
                }
                catch (Exception e)
                {
                    automaton.tcpClient = null;
                }
            }
        }

        protected class ConnectionTransition : Transition
        {
            public ConnectionTransition(BaseAutomaton owner) : base(owner) { }

            public override string TransitionCode()
            {
                AutomataTransferAutomaton automaton = (AutomataTransferAutomaton)Owner;
                if (automaton.tcpClient != null)
                {
                    return IDLE_STATE;
                }
                else
                {
                    return CONNECTION_STATE;
                }
            }
        }


        /* IDLE_STATE: Automaton waits for a message from the MessageRouter or from the threads. */
        protected class IdleTransition : Transition
        {
            public IdleTransition(BaseAutomaton owner) : base(owner) { }

            public override string TransitionCode()
            {
                AutomataTransferAutomaton automaton = (AutomataTransferAutomaton)Owner;
                automaton.GetNextMessage();
                if (automaton.currentMessage.From.Equals("AutomataTransferAutomaton"))
                {
                    if (automaton.currentMessage.Data.Equals(Errors.PULL_ERROR) ||
                        automaton.currentMessage.Data.Equals(Errors.PUSH_ERROR))
                    {
                        return CONNECTION_STATE;
                    }
                    else
                    {
                        return PROCESS_AUTOMATON_REQUEST;
                    }
                }
                else
                {
                    return SEND_AUTOMATON_STATE;
                }
            }
        }

        /* SEND_AUTOMATON_STATE: Automaton adds the automaton to the list of automatons to send. */
        protected class SendAutomatonState : State
        {
            public SendAutomatonState(BaseAutomaton owner) : base(owner) { }

            public override void StateCode()
            {
                AutomataTransferAutomaton automaton = (AutomataTransferAutomaton)Owner;
                automaton.automatonsToSend.Add(automaton.currentMessage);
            }
        }

        /* PROCESS_AUTOMATON_REQUEST: Responds to a request from the remote application by sending the specified automaton. */
        protected class ProcessAutomatonRequestState : State
        {
            public ProcessAutomatonRequestState(BaseAutomaton owner) : base(owner) { }

            public override void StateCode()
            {
                AutomataTransferAutomaton automaton = (AutomataTransferAutomaton)Owner;
                automaton.societyManager
                    .SendXMLAutomatonToRemoteApplication(automaton.currentMessage.Data.ToString());
            }
        }
	
	    protected void PullAutomatons(){
		    try {
			    XMLAutomaton automaton;
			    while(pullRunning){
				    automaton = new XMLAutomaton();
                    if (automaton.DeserializeFromStream(inputStream.BaseStream))
                    {
                        societyManager.AddAutomaton(automaton.Name, automaton);
                        automaton.Start();

                        MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                            new Message("AutomataTransferAutomaton", "Received automaton: " + automaton.Name + "\r\n"));
                    }
                    else
                    {
                        throw new Exception();
                    }
			    }
		    } catch (Exception e) {
                AddMessage(new Message("AutomataTransferAutomaton", Errors.PULL_ERROR));
                MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                    new Message("AutomataTransferAutomaton", "An error occured while pulling an automaton."));
		    }
	    }
	
	    protected void PushAutomatons(){
		    try {
			    Message message;
			    XMLAutomaton automaton;
			    while(pushRunning){
				    message = automatonsToSend.Remove();
                    if (message == null)
                    {
                        return;
                    }
				    automaton = (XMLAutomaton) message.Data;
                    automaton.RestoreSnapshot();
                    if (automaton.SerializeToStream(outputStream.BaseStream))
                    {
                        outputStream.Flush();

                        societyManager.RemoveAutomaton(automaton.Name);
                        MessageRouter.Instance.Unsubscribe(automaton.Name);

                        MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                            new Message("AutomataTransferAutomaton", "Sent automaton: " + automaton.Name + "\r\n"));
                    }
                    else
                    {
                        MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                            new Message("AutomataTransferAutomaton", "An error occured while serializing " + automaton.Name));
                    }
			    }
		    } catch (Exception e) {
                AddMessage(new Message("AutomataTransferAutomaton", Errors.PUSH_ERROR));
                MessageRouter.Instance.Send(SocietyInformation.UI_MANAGER_NAME,
                    new Message("AutomataTransferAutomaton", "An error occured while pushing an automaton."));
		    }
	    }
    }
}
